package com.openx.boot.core.exception;

import com.openx.boot.core.constants.ResultCode;
import com.openx.boot.core.model.ApiErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

/**
 * 统一Web异常处理器
 *
 * @author LYD
 * @date 2017/7/3
 */
@ControllerAdvice
@ResponseBody
@Slf4j
public class DefaultWebExceptionAdvice {

    private static ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();

    /**
     * 其他异常
     *
     * @param ex
     * @param request
     * @param response
     * @return
     */
    @ExceptionHandler({Exception.class})
    public ResponseEntity<ApiErrorResponse> exception(Exception ex, HttpServletRequest request, HttpServletResponse response) {
        return resolveException(ex, request.getRequestURI());
    }

    /**
     * 静态解析异常。可以直接调用
     *
     * @param ex
     * @return
     */
    public static ResponseEntity<ApiErrorResponse> resolveException(Exception ex, String path) {
        ApiErrorResponse error = ApiErrorResponse.error(ResultCode.SYSTEM_ERROR, ex.getMessage());
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
        String className = ex.getClass().getName();
        if (className.contains("NoHandlerFoundException")) {
            error.setErrorCode(ResultCode.NOT_FOUND);
            return buildResponseEntity(ex, error, path, false);
        }
        if (className.contains("HttpRequestMethodNotSupportedException")) {
            error.setErrorCode(ResultCode.METHOD_NOT_ALLOWED);
            return buildResponseEntity(ex, error, path, false);
        }
        if (className.contains("HttpMediaTypeNotSupportedException")) {
            error.setErrorCode(ResultCode.MEDIA_TYPE_NOT_ACCEPTABLE);
            return buildResponseEntity(ex, error, path, false);
        }
        if (className.equals("org.springframework.web.bind.MissingServletRequestParameterException")) {
            error = ApiErrorResponse.error(ResultCode.BAD_REQUEST, ex.getMessage());
            return buildResponseEntity(ex, error, path, false);
        }
        if (className.equals("org.springframework.validation.BindException")) {
            String message = ((BindException) ex).getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
            error = ApiErrorResponse.error(ResultCode.BAD_REQUEST, ex.getMessage());
            error.setMessage(message);
            return buildResponseEntity(ex, error, path, false);
        }

        if (className.equals("javax.validation.ConstraintViolationException")) {
            String message = ((ConstraintViolationException) ex).getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
            error = ApiErrorResponse.error(ResultCode.BAD_REQUEST, ex.getMessage());
            error.setMessage(message);
            return buildResponseEntity(ex, error, path, false);
        }
        if (className.equals("org.springframework.web.bind.MethodArgumentNotValidException")) {
            String message = ((MethodArgumentNotValidException) ex).getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
            error = ApiErrorResponse.error(ResultCode.BAD_REQUEST, ex.getMessage());
            error.setMessage(message);
            return buildResponseEntity(ex, error, path, false);
        }

        Exception ase = (IllegalArgumentException) throwableAnalyzer.getFirstThrowableOfType(IllegalArgumentException.class, causeChain);
        if (ase != null) {
            error = ApiErrorResponse.error(ResultCode.BAD_REQUEST, ex.getMessage());
            error.setMessage(ex.getMessage());
            return buildResponseEntity(ex, error, path, false);
        }

        // oauth2异常处理
        if (ase != null) {
            if (ase.getMessage() != null) {
                if ("Bad credentials".contains(ase.getMessage())) {
                    error.setErrorCode(ResultCode.BAD_CREDENTIALS);
                } else if ("User is disabled".contains(ase.getMessage())) {
                    error.setErrorCode(ResultCode.ACCOUNT_DISABLED);
                } else if ("User account is locked".contains(ase.getMessage())) {
                    error.setErrorCode(ResultCode.ACCOUNT_LOCKED);
                } else {
                    error.setErrorCode(ResultCode.UNAUTHORIZED);
                }
            } else {
                error.setErrorCode(ResultCode.UNAUTHORIZED);
            }
            return buildResponseEntity(ex, error, path, false);
        }

        // 认证错误异常处理
        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,
                causeChain);
        if (ase != null) {
            if (ase.getMessage() != null) {
                if ("Bad credentials".contains(ase.getMessage())) {
                    error.setErrorCode(ResultCode.BAD_CREDENTIALS);
                } else if ("User is disabled".contains(ase.getMessage())) {
                    error.setErrorCode(ResultCode.ACCOUNT_DISABLED);
                } else if ("User account is locked".contains(ase.getMessage())) {
                    error.setErrorCode(ResultCode.ACCOUNT_LOCKED);
                } else {
                    error.setErrorCode(ResultCode.UNAUTHORIZED);
                }
            } else {
                error.setErrorCode(ResultCode.UNAUTHORIZED);
            }
            return buildResponseEntity(ex, error, path, false);
        }

        // 权限不足异常处理
        ase = (AccessDeniedException) throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (ase != null && ase instanceof AccessDeniedException) {
            ResultCode code = ResultCode.valueOfMessage(ex.getMessage());
            if (code.getCode() == ResultCode.SYSTEM_ERROR.getCode()) {
                code = ResultCode.ACCESS_DENIED;
            }
            error.setErrorCode(code);
            return buildResponseEntity(ex, error, path, false);
        }

        // 签名错误
        ase = (BaseSignatureErrorException) throwableAnalyzer
                .getFirstThrowableOfType(BaseSignatureErrorException.class, causeChain);
        if (ase != null && ase instanceof BaseSignatureErrorException) {
            error.setErrorCode(ResultCode.SIGNATURE_DENIED);
            return buildResponseEntity(ex, error, path, false);
        }

        // 自定义Feign调用异常
        ase = (BaseFeignErrorException) throwableAnalyzer
                .getFirstThrowableOfType(BaseFeignErrorException.class, causeChain);
        if (ase != null && ase instanceof BaseFeignErrorException) {
            BaseFeignErrorException fail = ((BaseFeignErrorException) ase);
            error.setCode(fail.getCode());
            error.setMessage(fail.getMessage());
            error.setError(fail.getMessage());
            return buildResponseEntity(ex, error, path, false);
        }

        // 自定义业务异常处理
        ase = (BaseFailException) throwableAnalyzer
                .getFirstThrowableOfType(BaseFailException.class, causeChain);
        if (ase != null && ase instanceof BaseFailException) {
            BaseFailException fail = ((BaseFailException) ase);
            error.setCode(fail.getCode());
            error.setMessage(fail.getMessage());
            error.setError(fail.getMessage());
            return buildResponseEntity(ex, error, path, true);
        }
        return buildResponseEntity(ex, error, path, false);
    }

    private static ResponseEntity<ApiErrorResponse> buildResponseEntity(Exception ex, ApiErrorResponse error, String path, boolean ok) {
        error.setPath(path);
        ResponseEntity responseEntity = ResponseEntity.status(ok ? HttpStatus.OK : ResultCode.valueOfCode(error.getCode()).getHttpStatus()).body(error);
        if (ok) {
            log.warn("异常解析: {},{}", responseEntity, ex.getMessage());
        } else {
            log.error("异常解析: {}", responseEntity, ex);
        }
        return responseEntity;
    }
}
